package jannex.sql.extensions;

import jannex.Builder;
import jannex.Jannex;
import jannex.mapping.ResultSetHandler;
import jannex.internal.ExceptionFactory;
import jannex.sql.Query;
import jannex.internal.CloseUtils;
import jannex.query.QueryBuilder;
import jannex.sql.JxConnection;
import jannex.sql.JxDataSource;
import jannex.sql.JxDatabaseMetaData;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.SQLException;
import java.util.logging.Logger;

public class DataSourceImpl implements JxDataSource {

    private final DataSource dataSource;
    private final Jannex jannex;

    private JxDatabaseMetaData cachedMetaData;

    public DataSourceImpl(DataSource dataSource, Jannex jannex) {
        this.dataSource = dataSource;
        this.jannex = jannex;
    }

    private Builder<JxConnection> builder = new Builder<JxConnection>() {
        @Override
        public JxConnection build() {
            try {
                return new ConnectionImpl(dataSource.getConnection(), jannex);
            } catch (SQLException e) {
                throw ExceptionFactory.make(e);
            }
        }
    };

    @Override
    public Query query(String sql) {
        return new Query(builder, jannex, sql);
    }

    @Override
    public <E> QueryBuilder<E> queryBuilder(Class<E> elementClass) {
        return queryBuilder(jannex.getHandler(elementClass));
    }

    @Override
    public <E> QueryBuilder<E> queryBuilder(ResultSetHandler<E> handler) {
        return new QueryBuilder<>(handler);
    }

    @Override
    public JxConnection getConnection() {
        try {
            return new ConnectionImpl(dataSource.getConnection(), jannex);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public JxConnection getConnection(String username, String password) {
        try {
            return new ConnectionImpl(dataSource.getConnection(username, password), jannex);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public JxDatabaseMetaData getCachedMetaData() {
        if (cachedMetaData == null) {
            try (JxConnection c = getConnection()){
                JxDatabaseMetaData databaseMetaData = c.getMetaData();
                cachedMetaData = DatabaseMetaDataProxy.newInstance(databaseMetaData);
            }
        }
        return cachedMetaData;
    }

    @Override
    public JxDatabaseMetaData getMetaData() {
        JxConnection connection = null;
        try {
            connection = getConnection();
            final JxConnection c = connection;
            return new DatabaseMetaDataImpl(c.getMetaData(), jannex) {
                @Override
                public void close() {
                    c.close();
                }
            };
        } catch (RuntimeException e) {
            throw CloseUtils.close(connection, e);
        }
    }

    @Override
    public PrintWriter getLogWriter() {
        try {
            return dataSource.getLogWriter();
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setLogWriter(PrintWriter out) {
        try {
            dataSource.setLogWriter(out);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setLoginTimeout(int seconds) {
        try {
            dataSource.setLoginTimeout(seconds);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public int getLoginTimeout() {
        try {
            return dataSource.getLoginTimeout();
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public Logger getParentLogger() {
        try {
            return dataSource.getParentLogger();
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T unwrap(Class<T> iface) {
        try {
            if (iface.equals(DataSource.class)) {
                return (T) dataSource;
            }
            return dataSource.unwrap(iface);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) {
        try {
            return iface.equals(DataSource.class) || dataSource.isWrapperFor(iface);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }
}
